<?
/**
 * This file is part of XNAT light.
 *
 * XNAT light is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * XNAT light is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XNAT light.  If not, see <http://www.gnu.org/licenses/>.
 */
namespace NRG\Common;

use NRG\Xnat as Xnat;
use NRG\Framework\Common\SessionFactory as SessionFactory;
use NRG\Exceptions\RealmException as RealmException;
use NRG\Framework\Exceptions\AuthException as AuthException;
use NRG\Framework as Framework;

/**
 * Login manager
 *
 * @package NRG\Common
 */
class LoginManager
{
    /**
     * Credential from query string
     *
     * @var int
     */
    const GET = 1;

    /**
     * Credential from POST data
     *
     * @var int
     */
    const POST = 2;

    /**
     * Credential from Request data (GET or POST)
     *
     * @var int
     */
    const REQUEST = 3;

    /**
     * Credential from HTTP Basic authentication
     *
     * @var int
     */
    const BASIC = 3;

    /**
     * Credential from HTTP Digest authentication
     *
     * @var int
     */
    const DIGEST = 4;

    /**
     * Credential from OpenID
     *
     * @var int
     */
    const OPENID = 5;

    /**
     * Credential from OAuth
     *
     * @var int
     */
    const OAUTH = 6;

    /**
     * XNAT Realm
     *
     * @var string
     */
    protected $_realm;

    /**
     * Login method
     *
     * @var int
     */
    protected $_method;

    /**
     * Constructor
     *
     * @param string $realm
     * @param string $method
     */
    public function __construct($realm, $method=self::REQUEST)
    {
        $this->_realm = $realm;
        $this->_method = $method;
    }

    /**
     * Contractual login method
     *
     * @return boolean
     * @throws AuthenticationException
     */
    public function login()
    {
        $realm = "NRG\Xnat\\" . $this->_realm;

        if(! class_exists($realm))
            throw new RealmException(RealmException::NO_SUCH_REALM, $realm);

        $session = SessionFactory::getSession();
        $loggedin = (bool) $session['loggedin'];

        $credentials = array();
        $xnat = null;

        if($loggedin || $session['axis.isAxisSession'] == 1)
        {
            $xml = new \SimpleXmlElement($session["user"]);

            $username = $xml->xpath("/XFTItem/login");
            $password = $xml->xpath("/XFTItem/primary_password");

            $credentials["username"] = (string) $username[0];
            $credentials["password"] = (string) $password[0];

            $xnat = new $realm($credentials["username"], $credentials["password"], true);
        }
        else
        {
            $credentials = $this->getCredentials();

            if($credentials["username"] == null || $credentials["username"] == "")
                throw new AuthException(AuthException::NO_USERNAME);
            else if($credentials["password"] == null || $credentials["password"] == "")
                throw new AuthException(AuthException::NO_PASSWORD);

            $xnat = new $realm($credentials["username"], $credentials["password"]);
        }

        return true;
    }

    /**
     * Checks whether the user is loggedin.
     * @return bool
     */
    public function isLoggedIn()
    {
        $result = false;

        $session = SessionFactory::getSession();

        // has the user logged in via the UI?
        $result = (bool) $session['loggedin'];

        // Has the user logged in via CreateServiceSession?
        if(! $result && ! empty($session["user"]) && $session['axis.isAxisSession'] == 1)
            $result = true;

        return $result;
    }

    /**
     * Get credentials from various sources
     *
     * @return array
     */
    public function getCredentials()
    {
        $session = SessionFactory::getSession();
        $credentials = array();

        switch($this->_method)
        {
            case self::GET:
                $credentials["username"] = $session->getQueryParam("username", false);
                $credentials["password"] = $session->getQueryParam("password", false);
                break;
            case self::POST:
                $credentials["username"] = $session->getPostParam("username", false);
                $credentials["password"] = $session->getPostParam("password", false);
                break;
            case self::REQUEST:
		        $credentials["username"] = $session->getRequestParam("username", false);
		        $credentials["password"] = $session->getRequestParam("password", false);
            case self::BASIC:
                if(!empty($_SERVER["PHP_AUTH_USER"]) && !empty($_SERVER["PHP_AUTH_PW"]))
                {
                    $credentials["username"] = $_SERVER["PHP_AUTH_USER"];
                    $credentials["password"] = $_SERVER["PHP_AUTH_PW"];
                }
                else
                { 
                    header('HTTP/1.0 401 Unauthorized');
                    header('WWW-Authenticate: Basic realm="' . $this->_realm . '"');

                    exit(1);
                }
		break;
            case self::DIGEST:
                /**
                 * HTTP Digest Authentiation will not be possible unless
                 * XNAT were to store HA1 (preferred) or the plain text
                 * password in the database. It's not worth moving forward 
                 * with this.
                 */
                if(!empty($_SERVER["PHP_AUTH_DIGEST"]))
                {
                    $password = ""; // we would need to retrieve this or have the digest in the database
                    $data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
                    $HA1 = md5($data["username"] . ":" . $realm . ":" . $password);
                    $HA2 = md5($_SERVER["REQUEST_METHOD"] . ":" . $data["uri"]);
                    $valid_response = md5($HA1 . ":" . $data["nonce"] . ":" . $data["nc"] . ":" . 
                            $data["cnonce"] . ":" . $data["qop"] . ":" . $HA2);
                
                    if($data["response"] != $valid_response)
                    {
                        $credentials["username"] = $data["username"];
                        $credentials["password"] = $password;
                    }
                }
                else
                {
                    header('HTTP/1.1 401 Unauthorized');
                    header('WWW-Authenticate: Digest realm="' . $this->_realm . '",qop="auth",nonce="' . 
                        uniqid(). '",opaque="' . md5($this->_realm) . '"');

                    exit(1);
                }
            case self::OPENID:
                header('HTTP/1.1 501 Not Implemented');
                break;
            case self::OPENAUTH:
                header('HTTP/1.1 501 Not Implemented');
                break;
        }

        return $credentials;
    }

    /**
     * Parse HTTP Digest
     *
     * @param text: string
     * @return: array
     */
    protected function http_digest_parse($txt)
    {
        // protect against missing data
        $needed_parts = array("nonce" => 1, "nc" => 1, "cnonce" => 1, "qop" => 1, "username" => 1, "uri" => 1, "response" => 1);
        $data = array();
        $keys = implode("|", array_keys($needed_parts));

        preg_match_all("@(" . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);

        foreach($matches as $m)
        {
            $data[$m[1]] = $m[3] ? $m[3] : $m[4];
            unset($needed_parts[$m[1]]);
        }

        return $needed_parts ? false : $data;
    }
}
